---
title: Node.js Backend Development
description: Master Node.js backend development coming from Python. Learn Express.js, database integration, authentication, real-time features, and deployment strategies.
---

# Node.js Backend Development

In this module, we'll dive deep into Node.js backend development from a Python developer's perspective. You'll learn how to build robust server-side applications using JavaScript, leveraging your existing backend development knowledge.

## Node.js Runtime Environment

### Python vs Node.js Runtime Comparison

<PythonEditor title="Runtime Environment Comparison" compare={true}>
```python !! py
# Python server environment
import os
import sys
import platform
from datetime import datetime

def server_info():
    """Display Python server environment information"""
    return {
        'runtime': 'Python',
        'version': sys.version,
        'platform': platform.platform(),
        'architecture': platform.architecture(),
        'executable': sys.executable,
        'path': sys.path[:3],  # First 3 paths
        'environment': {
            'HOME': os.environ.get('HOME', 'N/A'),
            'PATH': os.environ.get('PATH', 'N/A')[:100] + '...',
            'PYTHON_PATH': os.environ.get('PYTHONPATH', 'Not set')
        },
        'process_id': os.getpid(),
        'current_time': datetime.now().isoformat()
    }

# WSGI/ASGI application structure
def wsgi_application(environ, start_response):
    """Simple WSGI application"""
    status = '200 OK'
    headers = [('Content-type', 'application/json')]
    start_response(status, headers)
    
    info = server_info()
    import json
    return [json.dumps(info, indent=2).encode('utf-8')]

# Example usage
if __name__ == '__main__':
    info = server_info()
    print("Python Server Environment:")
    for key, value in info.items():
        print(f"{key}: {value}")
```

```javascript !! js
// Node.js server environment
const os = require('os');
const process = require('process');
const path = require('path');

function serverInfo() {
    /**
     * Display Node.js server environment information
     */
    return {
        runtime: 'Node.js',
        version: process.version,
        platform: os.platform(),
        architecture: os.arch(),
        executable: process.execPath,
        nodeModulesPath: path.join(__dirname, 'node_modules'),
        environment: {
            HOME: process.env.HOME || 'N/A',
            PATH: (process.env.PATH || 'N/A').substring(0, 100) + '...',
            NODE_PATH: process.env.NODE_PATH || 'Not set',
            NODE_ENV: process.env.NODE_ENV || 'development'
        },
        processId: process.pid,
        uptime: process.uptime(),
        memoryUsage: process.memoryUsage(),
        currentTime: new Date().toISOString()
    };
}

// Express.js application structure
const express = require('express');
const app = express();

app.get('/info', (req, res) => {
    const info = serverInfo();
    res.json(info);
});

// Example usage
if (require.main === module) {
    const info = serverInfo();
    console.log('Node.js Server Environment:');
    Object.entries(info).forEach(([key, value]) => {
        console.log(`${key}:`, value);
    });
    
    // Start server
    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
        console.log(`Server running on port ${PORT}`);
    });
}

module.exports = { serverInfo, app };
```
</PythonEditor>

## Express.js Advanced Features

### Routing and Middleware Ecosystem

<PythonEditor title="Advanced Express.js Routing" compare={true}>
```python !! py
# Django advanced URL routing and middleware
from django.urls import path, include
from django.http import JsonResponse
from django.middleware.csrf import CsrfViewMiddleware
from django.contrib.auth.decorators import login_required
import time
import logging

# Custom middleware
class RequestLoggingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        self.logger = logging.getLogger(__name__)

    def __call__(self, request):
        start_time = time.time()
        
        # Log request
        self.logger.info(f"Request: {request.method} {request.path}")
        
        response = self.get_response(request)
        
        # Log response with timing
        duration = time.time() - start_time
        self.logger.info(f"Response: {response.status_code} ({duration:.3f}s)")
        
        return response

# API views with decorators
@login_required
def api_users(request):
    if request.method == 'GET':
        users = [
            {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'},
            {'id': 2, 'name': 'Bob', 'email': 'bob@example.com'}
        ]
        return JsonResponse({'users': users})

# Nested URL patterns
api_patterns = [
    path('users/', api_users, name='api_users'),
    path('posts/', include('blog.urls')),
]

urlpatterns = [
    path('api/v1/', include(api_patterns)),
    path('admin/', admin.site.urls),
]
```

```javascript !! js
// Express.js advanced routing and middleware
const express = require('express');
const morgan = require('morgan');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');

const app = express();

// Security middleware
app.use(helmet());
app.use(cors({
    origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
    credentials: true
}));

// Request logging middleware
app.use(morgan('combined'));

// Rate limiting
const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // Limit each IP to 100 requests per windowMs
    message: 'Too many requests from this IP'
});
app.use('/api/', limiter);

// Body parsing middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

// Custom authentication middleware
const authMiddleware = (req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];
    
    if (!token) {
        return res.status(401).json({ error: 'No token provided' });
    }
    
    // Simplified token validation
    try {
        // In real app, verify JWT token
        req.user = { id: 1, name: 'Authenticated User' };
        next();
    } catch (error) {
        res.status(401).json({ error: 'Invalid token' });
    }
};

// Router for API v1
const apiV1Router = express.Router();

// Users endpoint with authentication
apiV1Router.get('/users', authMiddleware, (req, res) => {
    const users = [
        { id: 1, name: 'Alice', email: 'alice@example.com' },
        { id: 2, name: 'Bob', email: 'bob@example.com' }
    ];
    res.json({ users });
});

// Posts router (nested)
const postsRouter = express.Router();
postsRouter.get('/', (req, res) => {
    res.json({ posts: [] });
});
postsRouter.get('/:id', (req, res) => {
    res.json({ post: { id: req.params.id } });
});

apiV1Router.use('/posts', postsRouter);

// Mount API router
app.use('/api/v1', apiV1Router);

// Error handling middleware
app.use((error, req, res, next) => {
    console.error('Error:', error);
    res.status(500).json({ 
        error: 'Internal server error',
        message: error.message 
    });
});

// 404 handler
app.use((req, res) => {
    res.status(404).json({ error: 'Route not found' });
});

module.exports = app;
```
</PythonEditor>

## Database Integration

### MongoDB with Mongoose vs Django ORM

<PythonEditor title="Database Operations Comparison" compare={true}>
```python !! py
# Django ORM models and operations
from django.db import models
from django.contrib.auth.models import User
from datetime import datetime

class BlogPost(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    published = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    tags = models.ManyToManyField('Tag', blank=True)
    
    class Meta:
        ordering = ['-created_at']
    
    def __str__(self):
        return self.title

class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)
    
    def __str__(self):
        return self.name

# Database operations
def blog_operations():
    # Create
    post = BlogPost.objects.create(
        title="Learning Node.js",
        content="Node.js is a powerful runtime...",
        author_id=1,
        published=True
    )
    
    # Read with relationships
    posts = BlogPost.objects.select_related('author').prefetch_related('tags').filter(
        published=True
    )
    
    # Update
    BlogPost.objects.filter(id=post.id).update(
        title="Advanced Node.js",
        updated_at=datetime.now()
    )
    
    # Complex queries
    recent_posts = BlogPost.objects.filter(
        created_at__gte=datetime.now() - timedelta(days=7),
        published=True
    ).order_by('-created_at')[:10]
    
    # Aggregation
    from django.db.models import Count
    tag_counts = Tag.objects.annotate(
        post_count=Count('blogpost')
    ).order_by('-post_count')
    
    return {
        'created_post': post,
        'posts': list(posts.values()),
        'recent_posts': list(recent_posts.values()),
        'tag_counts': list(tag_counts.values())
    }
```

```javascript !! js
// Mongoose models and operations
const mongoose = require('mongoose');

// Define schemas
const tagSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
        unique: true,
        maxlength: 50
    }
});

const userSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true,
        unique: true
    },
    email: {
        type: String,
        required: true,
        unique: true
    },
    password: {
        type: String,
        required: true
    }
});

const blogPostSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true,
        maxlength: 200
    },
    content: {
        type: String,
        required: true
    },
    author: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
        required: true
    },
    published: {
        type: Boolean,
        default: false
    },
    tags: [{
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Tag'
    }],
    createdAt: {
        type: Date,
        default: Date.now
    },
    updatedAt: {
        type: Date,
        default: Date.now
    }
});

// Middleware for updating updatedAt
blogPostSchema.pre('save', function(next) {
    this.updatedAt = Date.now();
    next();
});

// Create models
const Tag = mongoose.model('Tag', tagSchema);
const User = mongoose.model('User', userSchema);
const BlogPost = mongoose.model('BlogPost', blogPostSchema);

// Database operations
async function blogOperations() {
    try {
        // Create
        const post = await BlogPost.create({
            title: "Learning Node.js",
            content: "Node.js is a powerful runtime...",
            author: new mongoose.Types.ObjectId(), // Assuming user exists
            published: true
        });
        
        // Read with relationships (population)
        const posts = await BlogPost
            .find({ published: true })
            .populate('author', 'username email')
            .populate('tags', 'name')
            .sort({ createdAt: -1 });
        
        // Update
        await BlogPost.findByIdAndUpdate(
            post._id,
            { 
                title: "Advanced Node.js",
                updatedAt: new Date()
            },
            { new: true }
        );
        
        // Complex queries
        const sevenDaysAgo = new Date();
        sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
        
        const recentPosts = await BlogPost
            .find({
                createdAt: { $gte: sevenDaysAgo },
                published: true
            })
            .sort({ createdAt: -1 })
            .limit(10);
        
        // Aggregation
        const tagCounts = await BlogPost.aggregate([
            { $unwind: '$tags' },
            { $group: { 
                _id: '$tags', 
                postCount: { $sum: 1 } 
            }},
            { $lookup: {
                from: 'tags',
                localField: '_id',
                foreignField: '_id',
                as: 'tagInfo'
            }},
            { $sort: { postCount: -1 } }
        ]);
        
        return {
            createdPost: post,
            posts: posts,
            recentPosts: recentPosts,
            tagCounts: tagCounts
        };
        
    } catch (error) {
        console.error('Database operation error:', error);
        throw error;
    }
}

module.exports = { BlogPost, User, Tag, blogOperations };
```
</PythonEditor>

## Authentication and Authorization

### JWT Authentication System

<PythonEditor title="Authentication Implementation" compare={true}>
```python !! py
# Django JWT authentication
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
import jwt
import datetime
from django.conf import settings
import json

@csrf_exempt
def login_view(request):
    if request.method == 'POST':
        data = json.loads(request.body)
        username = data.get('username')
        password = data.get('password')
        
        user = authenticate(username=username, password=password)
        if user:
            # Generate JWT token
            payload = {
                'user_id': user.id,
                'username': user.username,
                'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24),
                'iat': datetime.datetime.utcnow()
            }
            token = jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256')
            
            return JsonResponse({
                'token': token,
                'user': {
                    'id': user.id,
                    'username': user.username,
                    'email': user.email
                }
            })
        else:
            return JsonResponse({'error': 'Invalid credentials'}, status=401)

def jwt_authentication_middleware(get_response):
    def middleware(request):
        # Skip authentication for login endpoint
        if request.path == '/api/login/':
            return get_response(request)
        
        auth_header = request.META.get('HTTP_AUTHORIZATION')
        if auth_header and auth_header.startswith('Bearer '):
            token = auth_header.split(' ')[1]
            try:
                payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
                request.user_id = payload['user_id']
                request.username = payload['username']
            except jwt.ExpiredSignatureError:
                return JsonResponse({'error': 'Token expired'}, status=401)
            except jwt.InvalidTokenError:
                return JsonResponse({'error': 'Invalid token'}, status=401)
        
        return get_response(request)
    return middleware

# Protected view
def protected_view(request):
    if not hasattr(request, 'user_id'):
        return JsonResponse({'error': 'Authentication required'}, status=401)
    
    return JsonResponse({
        'message': f'Hello {request.username}',
        'user_id': request.user_id
    })
```

```javascript !! js
// Express.js JWT authentication
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const User = require('./models/User'); // Assuming User model exists

const app = express();
app.use(express.json());

// JWT secret (should be in environment variables)
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';

// Login endpoint
app.post('/api/login', async (req, res) => {
    try {
        const { username, password } = req.body;
        
        // Find user (using Mongoose example)
        const user = await User.findOne({ username });
        if (!user) {
            return res.status(401).json({ error: 'Invalid credentials' });
        }
        
        // Check password
        const isValidPassword = await bcrypt.compare(password, user.password);
        if (!isValidPassword) {
            return res.status(401).json({ error: 'Invalid credentials' });
        }
        
        // Generate JWT token
        const payload = {
            userId: user._id,
            username: user.username,
            iat: Math.floor(Date.now() / 1000),
            exp: Math.floor(Date.now() / 1000) + (24 * 60 * 60) // 24 hours
        };
        
        const token = jwt.sign(payload, JWT_SECRET);
        
        res.json({
            token,
            user: {
                id: user._id,
                username: user.username,
                email: user.email
            }
        });
        
    } catch (error) {
        console.error('Login error:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
});

// JWT authentication middleware
const authenticateToken = (req, res, next) => {
    // Skip authentication for login endpoint
    if (req.path === '/api/login') {
        return next();
    }
    
    const authHeader = req.headers.authorization;
    const token = authHeader && authHeader.split(' ')[1];
    
    if (!token) {
        return res.status(401).json({ error: 'Authentication required' });
    }
    
    jwt.verify(token, JWT_SECRET, (err, decoded) => {
        if (err) {
            if (err.name === 'TokenExpiredError') {
                return res.status(401).json({ error: 'Token expired' });
            }
            return res.status(401).json({ error: 'Invalid token' });
        }
        
        req.userId = decoded.userId;
        req.username = decoded.username;
        next();
    });
};

// Apply authentication middleware globally
app.use(authenticateToken);

// Protected route
app.get('/api/protected', (req, res) => {
    res.json({
        message: `Hello ${req.username}`,
        userId: req.userId
    });
});

// User registration endpoint
app.post('/api/register', async (req, res) => {
    try {
        const { username, email, password } = req.body;
        
        // Hash password
        const saltRounds = 10;
        const hashedPassword = await bcrypt.hash(password, saltRounds);
        
        // Create user
        const user = await User.create({
            username,
            email,
            password: hashedPassword
        });
        
        res.status(201).json({
            message: 'User created successfully',
            user: {
                id: user._id,
                username: user.username,
                email: user.email
            }
        });
        
    } catch (error) {
        if (error.code === 11000) {
            res.status(400).json({ error: 'Username or email already exists' });
        } else {
            console.error('Registration error:', error);
            res.status(500).json({ error: 'Internal server error' });
        }
    }
});

module.exports = app;
```
</PythonEditor>

## Real-time Features with WebSockets

### Socket.IO vs Django Channels

<PythonEditor title="WebSocket Implementation" compare={true}>
```python !! py
# Django Channels WebSocket consumer
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from django.contrib.auth.models import User

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = f'chat_{self.room_name}'
        
        # Join room group
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )
        
        await self.accept()
        
        # Send welcome message
        await self.send(text_data=json.dumps({
            'type': 'welcome',
            'message': f'Welcome to room {self.room_name}'
        }))

    async def disconnect(self, close_code):
        # Leave room group
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        data = json.loads(text_data)
        message_type = data.get('type', 'message')
        
        if message_type == 'chat_message':
            message = data['message']
            username = data.get('username', 'Anonymous')
            
            # Save message to database
            await self.save_message(username, message)
            
            # Send message to room group
            await self.channel_layer.group_send(
                self.room_group_name,
                {
                    'type': 'chat_message',
                    'message': message,
                    'username': username,
                    'timestamp': datetime.now().isoformat()
                }
            )
        
        elif message_type == 'typing':
            # Broadcast typing indicator
            await self.channel_layer.group_send(
                self.room_group_name,
                {
                    'type': 'typing_indicator',
                    'username': data.get('username', 'Anonymous'),
                    'is_typing': data.get('is_typing', False)
                }
            )

    async def chat_message(self, event):
        await self.send(text_data=json.dumps({
            'type': 'message',
            'message': event['message'],
            'username': event['username'],
            'timestamp': event['timestamp']
        }))

    async def typing_indicator(self, event):
        await self.send(text_data=json.dumps({
            'type': 'typing',
            'username': event['username'],
            'is_typing': event['is_typing']
        }))

    @database_sync_to_async
    def save_message(self, username, message):
        # Save message to database
        from .models import ChatMessage
        ChatMessage.objects.create(
            room_name=self.room_name,
            username=username,
            message=message
        )
```

```javascript !! js
// Socket.IO with Express.js
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const cors = require('cors');

const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
    cors: {
        origin: "http://localhost:3000",
        methods: ["GET", "POST"]
    }
});

app.use(cors());
app.use(express.json());

// In-memory storage (use database in production)
const chatRooms = new Map();
const activeUsers = new Map();

// Socket.IO connection handling
io.on('connection', (socket) => {
    console.log('User connected:', socket.id);
    
    // Join room
    socket.on('join_room', ({ roomName, username }) => {
        socket.join(roomName);
        socket.roomName = roomName;
        socket.username = username;
        
        // Track active users
        if (!activeUsers.has(roomName)) {
            activeUsers.set(roomName, new Set());
        }
        activeUsers.get(roomName).add(username);
        
        // Send welcome message
        socket.emit('welcome', {
            type: 'welcome',
            message: `Welcome to room ${roomName}`
        });
        
        // Notify room about new user
        socket.to(roomName).emit('user_joined', {
            type: 'user_joined',
            username: username,
            message: `${username} joined the room`
        });
        
        // Send active users list
        io.to(roomName).emit('active_users', {
            type: 'active_users',
            users: Array.from(activeUsers.get(roomName))
        });
        
        // Send recent messages
        const recentMessages = getRecentMessages(roomName);
        socket.emit('recent_messages', {
            type: 'recent_messages',
            messages: recentMessages
        });
    });
    
    // Handle chat messages
    socket.on('chat_message', (data) => {
        const { message } = data;
        const roomName = socket.roomName;
        const username = socket.username;
        
        if (roomName && username && message) {
            const messageData = {
                type: 'message',
                message: message,
                username: username,
                timestamp: new Date().toISOString(),
                id: generateMessageId()
            };
            
            // Save message
            saveMessage(roomName, messageData);
            
            // Broadcast to room
            io.to(roomName).emit('chat_message', messageData);
        }
    });
    
    // Handle typing indicators
    socket.on('typing', (data) => {
        const { isTyping } = data;
        const roomName = socket.roomName;
        const username = socket.username;
        
        if (roomName && username) {
            socket.to(roomName).emit('typing_indicator', {
                type: 'typing',
                username: username,
                isTyping: isTyping
            });
        }
    });
    
    // Handle private messages
    socket.on('private_message', (data) => {
        const { targetUser, message } = data;
        const senderUsername = socket.username;
        
        // Find target user's socket
        const targetSocket = Array.from(io.sockets.sockets.values())
            .find(s => s.username === targetUser);
        
        if (targetSocket) {
            targetSocket.emit('private_message', {
                type: 'private_message',
                from: senderUsername,
                message: message,
                timestamp: new Date().toISOString()
            });
        }
    });
    
    // Handle disconnection
    socket.on('disconnect', () => {
        const roomName = socket.roomName;
        const username = socket.username;
        
        if (roomName && username) {
            // Remove from active users
            if (activeUsers.has(roomName)) {
                activeUsers.get(roomName).delete(username);
                
                // Notify room about user leaving
                socket.to(roomName).emit('user_left', {
                    type: 'user_left',
                    username: username,
                    message: `${username} left the room`
                });
                
                // Update active users list
                io.to(roomName).emit('active_users', {
                    type: 'active_users',
                    users: Array.from(activeUsers.get(roomName))
                });
            }
        }
        
        console.log('User disconnected:', socket.id);
    });
});

// Helper functions
function saveMessage(roomName, messageData) {
    if (!chatRooms.has(roomName)) {
        chatRooms.set(roomName, []);
    }
    
    const messages = chatRooms.get(roomName);
    messages.push(messageData);
    
    // Keep only last 100 messages per room
    if (messages.length > 100) {
        messages.splice(0, messages.length - 100);
    }
}

function getRecentMessages(roomName, limit = 50) {
    const messages = chatRooms.get(roomName) || [];
    return messages.slice(-limit);
}

function generateMessageId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
}

// REST API endpoints
app.get('/api/rooms/:roomName/messages', (req, res) => {
    const { roomName } = req.params;
    const { limit = 50, offset = 0 } = req.query;
    
    const messages = getRecentMessages(roomName, parseInt(limit));
    res.json({ messages });
});

app.get('/api/rooms/:roomName/users', (req, res) => {
    const { roomName } = req.params;
    const users = activeUsers.get(roomName) || new Set();
    res.json({ users: Array.from(users) });
});

const PORT = process.env.PORT || 3001;
server.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});

module.exports = { app, io };
```
</PythonEditor>

## File Handling and Uploads

### Multer vs Django File Handling

<PythonEditor title="File Upload Implementation" compare={true}>
```python !! py
# Django file upload handling
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
import os
import uuid
from PIL import Image
import json

@csrf_exempt
def file_upload_view(request):
    if request.method == 'POST':
        uploaded_file = request.FILES.get('file')
        
        if not uploaded_file:
            return JsonResponse({'error': 'No file provided'}, status=400)
        
        # Validate file type
        allowed_types = ['image/jpeg', 'image/png', 'image/gif']
        if uploaded_file.content_type not in allowed_types:
            return JsonResponse({'error': 'Invalid file type'}, status=400)
        
        # Validate file size (max 5MB)
        max_size = 5 * 1024 * 1024
        if uploaded_file.size > max_size:
            return JsonResponse({'error': 'File too large'}, status=400)
        
        # Generate unique filename
        file_extension = os.path.splitext(uploaded_file.name)[1]
        unique_filename = f"{uuid.uuid4()}{file_extension}"
        
        try:
            # Save original file
            file_path = default_storage.save(
                f"uploads/{unique_filename}", 
                ContentFile(uploaded_file.read())
            )
            
            # Create thumbnail if image
            if uploaded_file.content_type.startswith('image/'):
                thumbnail_path = create_thumbnail(file_path)
            else:
                thumbnail_path = None
            
            return JsonResponse({
                'success': True,
                'file': {
                    'filename': unique_filename,
                    'original_name': uploaded_file.name,
                    'size': uploaded_file.size,
                    'content_type': uploaded_file.content_type,
                    'url': default_storage.url(file_path),
                    'thumbnail_url': default_storage.url(thumbnail_path) if thumbnail_path else None
                }
            })
            
        except Exception as e:
            return JsonResponse({'error': str(e)}, status=500)

def create_thumbnail(file_path):
    """Create thumbnail for uploaded image"""
    try:
        with default_storage.open(file_path, 'rb') as file:
            image = Image.open(file)
            image.thumbnail((150, 150), Image.Resampling.LANCZOS)
            
            # Generate thumbnail filename
            filename = os.path.basename(file_path)
            name, ext = os.path.splitext(filename)
            thumbnail_filename = f"{name}_thumb{ext}"
            
            # Save thumbnail
            from io import BytesIO
            thumbnail_io = BytesIO()
            image.save(thumbnail_io, format=image.format)
            thumbnail_io.seek(0)
            
            thumbnail_path = default_storage.save(
                f"thumbnails/{thumbnail_filename}",
                ContentFile(thumbnail_io.read())
            )
            
            return thumbnail_path
    except Exception as e:
        print(f"Error creating thumbnail: {e}")
        return None

# Multiple file upload
@csrf_exempt
def multiple_file_upload_view(request):
    if request.method == 'POST':
        files = request.FILES.getlist('files')
        
        if not files:
            return JsonResponse({'error': 'No files provided'}, status=400)
        
        uploaded_files = []
        errors = []
        
        for uploaded_file in files:
            try:
                # Process each file
                result = process_single_file(uploaded_file)
                uploaded_files.append(result)
            except Exception as e:
                errors.append({
                    'filename': uploaded_file.name,
                    'error': str(e)
                })
        
        return JsonResponse({
            'uploaded_files': uploaded_files,
            'errors': errors
        })
```

```javascript !! js
// Express.js file upload with Multer
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs').promises;
const sharp = require('sharp'); // For image processing
const { v4: uuidv4 } = require('uuid');

const app = express();

// Configure multer for file uploads
const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        const uploadDir = 'uploads/';
        // Ensure upload directory exists
        fs.mkdir(uploadDir, { recursive: true })
            .then(() => cb(null, uploadDir))
            .catch(err => cb(err));
    },
    filename: (req, file, cb) => {
        // Generate unique filename
        const fileExtension = path.extname(file.originalname);
        const uniqueName = `${uuidv4()}${fileExtension}`;
        cb(null, uniqueName);
    }
});

// File filter
const fileFilter = (req, file, cb) => {
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    
    if (allowedTypes.includes(file.mimetype)) {
        cb(null, true);
    } else {
        cb(new Error('Invalid file type. Only JPEG, PNG, and GIF are allowed.'), false);
    }
};

// Configure multer
const upload = multer({
    storage: storage,
    fileFilter: fileFilter,
    limits: {
        fileSize: 5 * 1024 * 1024, // 5MB limit
        files: 10 // Maximum 10 files
    }
});

// Single file upload endpoint
app.post('/api/upload', upload.single('file'), async (req, res) => {
    try {
        if (!req.file) {
            return res.status(400).json({ error: 'No file provided' });
        }
        
        const file = req.file;
        let thumbnailPath = null;
        
        // Create thumbnail for images
        if (file.mimetype.startsWith('image/')) {
            thumbnailPath = await createThumbnail(file.path);
        }
        
        res.json({
            success: true,
            file: {
                filename: file.filename,
                originalName: file.originalname,
                size: file.size,
                contentType: file.mimetype,
                url: `/uploads/${file.filename}`,
                thumbnailUrl: thumbnailPath ? `/thumbnails/${path.basename(thumbnailPath)}` : null
            }
        });
        
    } catch (error) {
        console.error('Upload error:', error);
        res.status(500).json({ error: 'Upload failed: ' + error.message });
    }
});

// Multiple file upload endpoint
app.post('/api/upload-multiple', upload.array('files', 10), async (req, res) => {
    try {
        if (!req.files || req.files.length === 0) {
            return res.status(400).json({ error: 'No files provided' });
        }
        
        const uploadedFiles = [];
        const errors = [];
        
        for (const file of req.files) {
            try {
                const result = await processSingleFile(file);
                uploadedFiles.push(result);
            } catch (error) {
                errors.push({
                    filename: file.originalname,
                    error: error.message
                });
            }
        }
        
        res.json({
            uploadedFiles,
            errors
        });
        
    } catch (error) {
        console.error('Multiple upload error:', error);
        res.status(500).json({ error: 'Upload failed: ' + error.message });
    }
});

// Helper function to create thumbnail
async function createThumbnail(imagePath) {
    try {
        const thumbnailDir = 'thumbnails/';
        await fs.mkdir(thumbnailDir, { recursive: true });
        
        const filename = path.basename(imagePath);
        const name = path.parse(filename).name;
        const ext = path.parse(filename).ext;
        const thumbnailFilename = `${name}_thumb${ext}`;
        const thumbnailPath = path.join(thumbnailDir, thumbnailFilename);
        
        await sharp(imagePath)
            .resize(150, 150, {
                fit: 'inside',
                withoutEnlargement: true
            })
            .toFile(thumbnailPath);
        
        return thumbnailPath;
    } catch (error) {
        console.error('Error creating thumbnail:', error);
        return null;
    }
}

// Helper function to process single file
async function processSingleFile(file) {
    let thumbnailPath = null;
    
    if (file.mimetype.startsWith('image/')) {
        thumbnailPath = await createThumbnail(file.path);
    }
    
    return {
        filename: file.filename,
        originalName: file.originalname,
        size: file.size,
        contentType: file.mimetype,
        url: `/uploads/${file.filename}`,
        thumbnailUrl: thumbnailPath ? `/thumbnails/${path.basename(thumbnailPath)}` : null
    };
}

// Serve uploaded files
app.use('/uploads', express.static('uploads'));
app.use('/thumbnails', express.static('thumbnails'));

// Error handling middleware for multer
app.use((error, req, res, next) => {
    if (error instanceof multer.MulterError) {
        if (error.code === 'LIMIT_FILE_SIZE') {
            return res.status(400).json({ error: 'File too large' });
        }
        if (error.code === 'LIMIT_FILE_COUNT') {
            return res.status(400).json({ error: 'Too many files' });
        }
    }
    
    if (error.message.includes('Invalid file type')) {
        return res.status(400).json({ error: error.message });
    }
    
    res.status(500).json({ error: 'Upload error: ' + error.message });
});

module.exports = app;
```
</PythonEditor>

## Testing and Debugging

### Jest vs Python Testing

<PythonEditor title="Testing Strategies" compare={true}>
```python !! py
# Django/Python testing with pytest
import pytest
from django.test import TestCase, Client
from django.contrib.auth.models import User
from unittest.mock import patch, Mock
import json

class APITestCase(TestCase):
    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass'
        )
    
    def test_login_success(self):
        """Test successful login"""
        response = self.client.post('/api/login/', {
            'username': 'testuser',
            'password': 'testpass'
        }, content_type='application/json')
        
        self.assertEqual(response.status_code, 200)
        data = json.loads(response.content)
        self.assertIn('token', data)
        self.assertEqual(data['user']['username'], 'testuser')
    
    def test_login_invalid_credentials(self):
        """Test login with invalid credentials"""
        response = self.client.post('/api/login/', {
            'username': 'testuser',
            'password': 'wrongpass'
        }, content_type='application/json')
        
        self.assertEqual(response.status_code, 401)
    
    @patch('requests.get')
    def test_external_api_call(self, mock_get):
        """Test external API integration with mocking"""
        mock_response = Mock()
        mock_response.json.return_value = {'status': 'success'}
        mock_response.status_code = 200
        mock_get.return_value = mock_response
        
        # Test the function that makes external API call
        from myapp.utils import fetch_external_data
        result = fetch_external_data('test_id')
        
        self.assertEqual(result['status'], 'success')
        mock_get.assert_called_once()

# pytest fixtures and parametrized tests
@pytest.fixture
def authenticated_client():
    """Fixture for authenticated client"""
    client = Client()
    user = User.objects.create_user(
        username='testuser',
        password='testpass'
    )
    client.force_login(user)
    return client, user

@pytest.mark.parametrize("username,password,expected_status", [
    ('valid_user', 'valid_pass', 200),
    ('invalid_user', 'valid_pass', 401),
    ('valid_user', 'invalid_pass', 401),
    ('', '', 400),
])
def test_login_scenarios(username, password, expected_status):
    """Test multiple login scenarios"""
    client = Client()
    if username == 'valid_user':
        User.objects.create_user(username=username, password=password)
    
    response = client.post('/api/login/', {
        'username': username,
        'password': password
    }, content_type='application/json')
    
    assert response.status_code == expected_status
```

```javascript !! js
// Jest testing for Node.js/Express
const request = require('supertest');
const app = require('../app');
const User = require('../models/User');
const jwt = require('jsonwebtoken');

// Setup and teardown
describe('API Tests', () => {
    beforeEach(async () => {
        // Clear database before each test
        await User.deleteMany({});
        
        // Create test user
        await User.create({
            username: 'testuser',
            email: 'test@example.com',
            password: '$2a$10$hashed_password_here'
        });
    });
    
    afterEach(async () => {
        // Cleanup after each test
        await User.deleteMany({});
    });
    
    describe('POST /api/login', () => {
        it('should login successfully with valid credentials', async () => {
            const response = await request(app)
                .post('/api/login')
                .send({
                    username: 'testuser',
                    password: 'testpass'
                })
                .expect(200);
            
            expect(response.body).toHaveProperty('token');
            expect(response.body.user.username).toBe('testuser');
        });
        
        it('should fail with invalid credentials', async () => {
            const response = await request(app)
                .post('/api/login')
                .send({
                    username: 'testuser',
                    password: 'wrongpass'
                })
                .expect(401);
            
            expect(response.body).toHaveProperty('error');
        });
        
        it('should validate required fields', async () => {
            const response = await request(app)
                .post('/api/login')
                .send({})
                .expect(400);
            
            expect(response.body.error).toContain('required');
        });
    });
    
    describe('GET /api/protected', () => {
        let authToken;
        
        beforeEach(async () => {
            // Generate valid JWT token for tests
            authToken = jwt.sign(
                { userId: '507f1f77bcf86cd799439011', username: 'testuser' },
                process.env.JWT_SECRET || 'test-secret'
            );
        });
        
        it('should access protected route with valid token', async () => {
            const response = await request(app)
                .get('/api/protected')
                .set('Authorization', `Bearer ${authToken}`)
                .expect(200);
            
            expect(response.body.message).toContain('Hello testuser');
        });
        
        it('should reject request without token', async () => {
            await request(app)
                .get('/api/protected')
                .expect(401);
        });
        
        it('should reject request with invalid token', async () => {
            await request(app)
                .get('/api/protected')
                .set('Authorization', 'Bearer invalid_token')
                .expect(401);
        });
    });
});

// Mock external dependencies
jest.mock('../services/emailService', () => ({
    sendEmail: jest.fn().mockResolvedValue({ success: true })
}));

describe('External API Integration', () => {
    it('should handle external API call with mocking', async () => {
        // Mock axios or fetch
        const mockAxios = require('axios');
        mockAxios.get.mockResolvedValue({
            data: { status: 'success' },
            status: 200
        });
        
        const externalService = require('../services/externalService');
        const result = await externalService.fetchData('test_id');
        
        expect(result.status).toBe('success');
        expect(mockAxios.get).toHaveBeenCalledWith(
            expect.stringContaining('test_id')
        );
    });
});

// Parametrized tests using test.each
describe('Login scenarios', () => {
    test.each([
        ['valid_user', 'valid_pass', 200],
        ['invalid_user', 'valid_pass', 401],
        ['valid_user', 'invalid_pass', 401],
        ['', '', 400]
    ])('login with username: %s, password: %s should return %i', 
        async (username, password, expectedStatus) => {
            // Setup valid user if needed
            if (username === 'valid_user') {
                await User.create({
                    username,
                    password: '$2a$10$hashed_password_here'
                });
            }
            
            const response = await request(app)
                .post('/api/login')
                .send({ username, password });
            
            expect(response.status).toBe(expectedStatus);
        }
    );
});

// Test utilities and helpers
const testUtils = {
    createAuthenticatedUser: async (userData = {}) => {
        const defaultUser = {
            username: 'testuser',
            email: 'test@example.com',
            password: 'hashedpassword'
        };
        
        const user = await User.create({ ...defaultUser, ...userData });
        const token = jwt.sign(
            { userId: user._id, username: user.username },
            process.env.JWT_SECRET || 'test-secret'
        );
        
        return { user, token };
    },
    
    makeAuthenticatedRequest: (app, method, url, token) => {
        return request(app)[method](url)
            .set('Authorization', `Bearer ${token}`);
    }
};

module.exports = testUtils;
```
</PythonEditor>

## Summary and Best Practices

### Node.js Development Guidelines

1. **Environment Configuration**
   - Use environment variables for configuration
   - Implement proper error handling
   - Use process managers like PM2 for production

2. **Security Best Practices**
   - Implement rate limiting
   - Use helmet.js for security headers
   - Validate and sanitize input data
   - Implement proper authentication and authorization

3. **Performance Optimization**
   - Use connection pooling for databases
   - Implement caching strategies
   - Use compression middleware
   - Monitor memory usage and handle memory leaks

4. **Database Integration**
   - Choose between SQL (PostgreSQL) and NoSQL (MongoDB)
   - Implement proper indexing
   - Use transactions where appropriate
   - Handle database connection errors gracefully

5. **Testing Strategy**
   - Write unit tests for business logic
   - Implement integration tests for APIs
   - Use mocking for external dependencies
   - Maintain good test coverage

---

In the next module, we'll explore package management and the JavaScript ecosystem, learning how to leverage npm and build modern development workflows.
